package apollo

import (
	"crypto/tls"
	"fmt"
	"gitee.com/dennis-kk/service-box-go/util/errors"
	"io/ioutil"
	"net"
	"net/http"
	"net/url"
	"strings"
	"sync"
	"time"
)

var (
	//for on error retry
	onErrorRetryInterval = 2 * time.Second //2s

	connectTimeout = 1 * time.Second //1s

	//max retries connect apollo
	maxRetries = 5

	//defaultMaxConnsPerHost defines the maximum number of concurrent connections
	defaultMaxConnsPerHost = 512
	//defaultTimeoutBySecond defines the default timeout for http connections
	defaultTimeoutBySecond = 1 * time.Second
	//defaultKeepAliveSecond defines the connection time
	defaultKeepAliveSecond = 60 * time.Second
	// once for single http.Transport
	once sync.Once
	// defaultTransport http.Transport
	defaultTransport *http.Transport
)

func getDefaultTransport(insecureSkipVerify bool) *http.Transport {
	once.Do(func() {
		defaultTransport = &http.Transport{
			Proxy:               http.ProxyFromEnvironment,
			MaxIdleConns:        defaultMaxConnsPerHost,
			MaxIdleConnsPerHost: defaultMaxConnsPerHost,
			DialContext: (&net.Dialer{
				KeepAlive: defaultKeepAliveSecond,
				Timeout:   defaultTimeoutBySecond,
			}).DialContext,
		}
		if insecureSkipVerify {
			defaultTransport.TLSClientConfig = &tls.Config{
				InsecureSkipVerify: insecureSkipVerify,
			}
		}
	})
	return defaultTransport
}

//CallBack 请求回调函数
type CallBack struct {
	SuccessCallBack   func([]byte, CallBack) (configData, error)
	NotModifyCallBack func() error
	Namespace         string
}

//Request 建立网络请求
func Request(requestURL string, connectionConfig *Config, callBack *CallBack) (configData, error) {
	if connectionConfig == nil {
		return nil, errors.InvalidApolloCfg
	}

	client := &http.Client{}
	//如有设置自定义超时时间即使用
	if connectionConfig.Timeout != 0 {
		client.Timeout = connectionConfig.Timeout
	} else {
		client.Timeout = connectTimeout
	}
	var err error
	up, err := url.Parse(requestURL)
	if err != nil {
		fmt.Printf("request Apollo Server url:%s, is invalid %s", requestURL, err)
		return nil, err
	}
	var insecureSkipVerify bool
	if strings.HasPrefix(up.Scheme, "https") {
		insecureSkipVerify = true
	}
	client.Transport = getDefaultTransport(insecureSkipVerify)
	retry := 0
	var retries = maxRetries
	if !connectionConfig.IsRetry {
		retries = 1
	}
	for {

		retry++

		if retry > retries {
			break
		}
		req, err := http.NewRequest("GET", requestURL, nil)
		if req == nil || err != nil {
			fmt.Printf("Generate connect Apollo request Fail,url:%s,Error:%s", requestURL, err)
			// if error then sleep
			return nil, errors.ConnectApolloFailed
		}

		//增加header选项
		//httpAuth := connectionConfig.Auth
		if connectionConfig.Auth != nil {
			headers := connectionConfig.Auth.HTTPHeaders(requestURL, connectionConfig.AppId, connectionConfig.Secret)
			if len(headers) > 0 {
				req.Header = headers
			}
			host := req.Header.Get("Host")
			if len(host) > 0 {
				req.Host = host
			}
		}

		res, err := client.Do(req)
		if res != nil {
			defer res.Body.Close()
		}

		if res == nil || err != nil {
			fmt.Printf("Connect Apollo Server Fail,url:%s,Error:%s", requestURL, err)
			// if error then sleep
			time.Sleep(onErrorRetryInterval)
			continue
		}

		//not modified break
		switch res.StatusCode {
		case http.StatusOK:
			responseBody, err := ioutil.ReadAll(res.Body)
			if err != nil {
				fmt.Printf("Connect Apollo Server Fail,url : %s ,Error: %s ", requestURL, err)
				// if error then sleep
				time.Sleep(onErrorRetryInterval)
				continue
			}

			//设置了回调函数的，使用回调函数处理请求返回值
			if callBack != nil && callBack.SuccessCallBack != nil {
				return callBack.SuccessCallBack(responseBody, *callBack)
			}
			return responseBody, nil
		case http.StatusNotModified:
			fmt.Printf("Config Not Modified: %v", err)
			if callBack != nil && callBack.NotModifyCallBack != nil {
				return nil, callBack.NotModifyCallBack()
			}
			return nil, nil
		default:
			fmt.Printf("Connect Apollo Server Fail,url:%s,StatusCode:%d", requestURL, res.StatusCode)
			// if error then sleep
			time.Sleep(onErrorRetryInterval)
			continue
		}
	}

	fmt.Printf("Over Max Retry Still Error,Error: %v", err)
	if retry > retries {
		err = errors.ApolloOverMaxRetry
	}
	return nil, err
}
